package com.util.vmwareops;
import com.vmware.vim25.mo.ServiceInstance;

import java.util.HashSet;
import java.util.LinkedList;
import java.sql.Driver;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.concurrent.LinkedBlockingQueue;

public class DBConnectionPool implements ConnectionPool<Connection> {
    private LinkedBlockingQueue<Connection> freeConnection;
    private LinkedBlockingQueue<Connection> usedConnection;
    private String url;
    private String username;
    private String password;
    private int maxConnect;
    private int minConnect;
    final boolean DEBUG = false;
    private long lastClearClosedConnection;
    public static long CHECK_CLOSED_CONNECTION_TIME = 10000;
    public static long WAIT_TIME = 5000;

    //构造函数
    public DBConnectionPool(String dbName, String username, String password) {
        int maxConnect = 3;
        int minConnect = 1;
        this.init(dbName, username, password, maxConnect, minConnect);
    }
    //构造函数
    public DBConnectionPool(String dbName, String username, String password, int maxConnect, int minConnect) {
        this.init(dbName, username, password, maxConnect, minConnect);
    }
    // 构造函数
    private void init(String dbName, String username, String password, int maxConnect, int minConnect) {
        try {
            this.initDriver();
            this.url = "jdbc:mysql://localhost:3306/" + dbName + "?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true";
            this.username = username;
            this.password = password;
            this.freeConnection = new LinkedBlockingQueue<Connection>();
            this.usedConnection = new LinkedBlockingQueue<Connection>();
            this.lastClearClosedConnection = System.currentTimeMillis();
            this.maxConnect = maxConnect;
            this.minConnect = minConnect;
        } catch(InstantiationException e) {
            e.printStackTrace();
        } catch(IllegalAccessException e) {
            e.printStackTrace();
        } catch(ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    private void initDriver() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        Driver driver = null;
        driver = (Driver)Class.forName("com.mysql.cj.jdbc.Driver").newInstance();
        try {
            DriverManager.registerDriver(driver);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // 获取jdbc连接
    @Override
    public synchronized Connection getConnection() throws Exception {

        // 从空闲的连接池中获取一个未使用的连接
        while(this.freeConnection.size() > 0) {
            Connection con = (Connection)this.freeConnection.poll();
            if(this.isClosed(con)) {
                continue;
            }
            this.usedConnection.add(con);
            return con;
        }
        //先将过期的连接清理掉
        this.clearClosedConnection();

        //确保有可用的连接
        this.ensureFreeConnectionPool();
        // 如果没有可用连接，返回null
        Connection si = this.freeConnection.take();
        if(si != null) {
            usedConnection.offer(si);
        }
        return si;

    }
    @Override
    public boolean isClosed(Connection con) {
        boolean res = false;
        try {
            res = con.isClosed();
        } catch(SQLException e) {
            e.printStackTrace();
        }
        return res;
    }
    private void ensureFreeConnectionPool() {

        // 如果有可用的连接，直接返回
        if(this.freeConnection.size() > 0) {
            return;
        }
        // 如果空闲连接池为空，需要新建一些连接共应用使用
        // 如果当前已经建立的连接超过了定义的最大连接数，则返回null，表示没有可用的连接
        // 如果还有空间可以建立连接，则获取一个要建立的连接数newCount，newCount+已经建立的连接需要小于最大的连接数限制
        int currentConnectionCount = this.usedConnection.size() + this.freeConnection.size();

        if(currentConnectionCount >= this.maxConnect) {
            return;
        }
        int newCount = this.getIncreasingConnection();
        if(newCount > this.maxConnect - currentConnectionCount) {
            newCount = this.maxConnect - currentConnectionCount;
        }
        if(newCount + currentConnectionCount < this.minConnect) {
            newCount = this.minConnect - currentConnectionCount;
        }

        // 新建可用的连接加入到空闲连接池中
        Connection con = null;
        for(int i=0;i<newCount;i++) {
            con = this.getNewConnection();
            if(con != null ){
                this.freeConnection.add(con);
            }
        }
    }
    private Connection getNewConnection() {
        try {
            Connection con = DriverManager.getConnection(this.url, this.username, this.password);
            return con;
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }
    private int getIncreasingConnection() {
        int count = (this.usedConnection.size() + this.freeConnection.size()) / 5;
        return count >= 1 ? count : 1;
    }
    private void clearClosedConnection() {
        long time = System.currentTimeMillis();
        if(time - this.lastClearClosedConnection < CHECK_CLOSED_CONNECTION_TIME) {
            return;
        }
        this.lastClearClosedConnection = time;
        Iterator iterator = this.freeConnection.iterator();
        while(iterator.hasNext()) {
            Connection con = (Connection) iterator.next();
            try {
                if(con.isClosed()) {
                    iterator.remove();
                }
            } catch (SQLException e) {
                iterator.remove();
                e.printStackTrace();
            }
        }

    }
    private int closePool(Iterator iterator) {
        int count = 0;
        while(iterator.hasNext()) {
            try {
                ((Connection)iterator.next()).close();
                count++;
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return count;
    }
    @Override
    public int closeAll() {
        int count = 0;
        Iterator iterator = this.usedConnection.iterator();
        count += this.closePool(iterator);
        this.usedConnection.clear();

        iterator = this.freeConnection.iterator();
        count += this.closePool(iterator);
        this.freeConnection.clear();

        return count;
    }

    @Override
    public synchronized void close(Connection con) {
        boolean exist = this.usedConnection.remove(con);
        if(exist) {
            this.freeConnection.add(con);
        }
        System.out.println("[CLOSE] DB used " + this.usedConnection.size() + "; free " + this.freeConnection.size());

    }

}
